package site.zbox.tool.swing.xml;

import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.RootPaneContainer;
import javax.swing.border.Border;

import org.apache.commons.collections4.CollectionUtils;
import org.dom4j.Attribute;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import site.zbox.tool.image.ImageUtils;
import site.zbox.tool.lang.NumberUtils;
import site.zbox.tool.lang.StringUtils;
import site.zbox.tool.lang.Version;
import site.zbox.tool.swing.ComponentMap;
import site.zbox.tool.swing.xml.component.ButtonComponentParser;
import site.zbox.tool.swing.xml.component.ComboBoxComponentParser;
import site.zbox.tool.swing.xml.component.DesktopPaneComponentParser;
import site.zbox.tool.swing.xml.component.LabelComponentParser;
import site.zbox.tool.swing.xml.component.MenuComponentParser;
import site.zbox.tool.swing.xml.component.MenuItemComponentParser;
import site.zbox.tool.swing.xml.component.NullComponentParser;
import site.zbox.tool.swing.xml.component.PanelComponentParser;
import site.zbox.tool.swing.xml.component.ProgressBarComponentParser;
import site.zbox.tool.swing.xml.component.ScrollPaneComponentParser;
import site.zbox.tool.swing.xml.component.SplitPaneComponentParser;
import site.zbox.tool.swing.xml.component.TableComponentParser;
import site.zbox.tool.swing.xml.component.TextAreaComponentParser;
import site.zbox.tool.swing.xml.component.TextFieldComponentParser;
import site.zbox.tool.swing.xml.component.ToolBarComponentParser;
import site.zbox.tool.swing.xml.layout.BorderLayoutLayoutManagerParser;
import site.zbox.tool.swing.xml.layout.GridBagLayoutLayoutManagerParser;
import site.zbox.tool.swing.xml.layout.NullLayoutManagerParser;
import site.zbox.tool.swing.xml.layout.SplitPaneLayoutManagerParser;
import site.zbox.tool.xml.DocumentUtils;

@Version("2021.08.18")
public class XMLLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(XMLLoader.class);

    private static final Map<String, IComponentParser> COMPONENT_PARSER_MAP = new TreeMap<>();
    private static final Map<String, ILayoutManagerParser> LAYOUT_MANAGER_PARSER_MAP = new TreeMap<>();

    static {
        addComponentParser(NullComponentParser.INSTANCE);
        addComponentParser(new ButtonComponentParser());
        addComponentParser(new ComboBoxComponentParser());
        addComponentParser(new DesktopPaneComponentParser());
        addComponentParser(new LabelComponentParser());
        addComponentParser(new MenuItemComponentParser());
        addComponentParser(new MenuComponentParser());
        addComponentParser(new PanelComponentParser());
        addComponentParser(new ProgressBarComponentParser());
        addComponentParser(new ScrollPaneComponentParser());
        addComponentParser(new SplitPaneComponentParser());
        addComponentParser(new TableComponentParser());
        addComponentParser(new TextAreaComponentParser());
        addComponentParser(new TextFieldComponentParser());
        addComponentParser(new ToolBarComponentParser());

        addLayoutManagerParser(NullLayoutManagerParser.INSTANCE);
        addLayoutManagerParser(new GridBagLayoutLayoutManagerParser());
        addLayoutManagerParser(new BorderLayoutLayoutManagerParser());
        addLayoutManagerParser(new SplitPaneLayoutManagerParser());
    }

    public static void addLayoutManagerParser(final ILayoutManagerParser layoutManagerParser) {
        LAYOUT_MANAGER_PARSER_MAP.put(layoutManagerParser.name(), layoutManagerParser);
    }

    public static void addComponentParser(final IComponentParser componentParser) {
        COMPONENT_PARSER_MAP.put(componentParser.name(), componentParser);
    }

    public static ComponentMap load(final RootPaneContainer container) {
        final Class<? extends RootPaneContainer> containerClass = container.getClass();
        final URL url = containerClass.getResource(containerClass.getSimpleName() + ".component.xml");
        final ComponentMap componentMap = new ComponentMap();
        if (url == null) {
            return componentMap;
        }
        final JRootPane rootPane = container.getRootPane();
        final Element rootElement = DocumentUtils.newDocument(url).getRootElement();
        final Element menuBarElement = rootElement.element("MenuBar");
        final Element contentPaneElement = rootElement.element("ContentPane");
        if (menuBarElement != null) {
            final JMenuBar menuBar = new JMenuBar();
            setCommonAttribute(menuBarElement, menuBar);
            load(menuBar, menuBarElement.elements(), componentMap);
            rootPane.setJMenuBar(menuBar);
        }
        if (contentPaneElement != null) {
            final JComponent contentPane = (JComponent) rootPane.getContentPane();
            setCommonAttribute(contentPaneElement, contentPane);
            load(contentPane, contentPaneElement.elements(), componentMap);
        }
        final String icon = rootElement.attributeValue("icon");
        if (StringUtils.isNotEmpty(icon)) {
            final URL iconURL = containerClass.getResource(icon);
            if (iconURL != null) {
                try {
                    BufferedImage image;
                    try (InputStream stream = iconURL.openStream()) {
                        image = ImageUtils.read(stream).getFirst();
                    }
                    final String sizeString = rootElement.attributeValue("icon-size");
                    if (StringUtils.isNotEmpty(sizeString)) {
                        final int size = NumberUtils.toInt(sizeString.trim(), 32);
                        image = ImageUtils.newBufferedImage(image.getScaledInstance(size, size, Image.SCALE_DEFAULT));
                    }
                    if (container instanceof Window) {
                        ((Window) container).setIconImage(image);
                    } else if (container instanceof JInternalFrame) {
                        ((JInternalFrame) container).setFrameIcon(new ImageIcon(image));
                    }
                } catch (IOException e) {
                    LOGGER.warn(e.getMessage(), e);
                }
            }
        }
        return componentMap;
    }

    private static ILayoutManagerParser getLayoutManagerParser(final String name) {
        if (StringUtils.isEmpty(name)) {
            return NullLayoutManagerParser.INSTANCE;
        }
        final ILayoutManagerParser layoutManagerParser = LAYOUT_MANAGER_PARSER_MAP.get(name);
        if (layoutManagerParser == null) {
            return NullLayoutManagerParser.INSTANCE;
        }
        return layoutManagerParser;
    }

    private static ILayoutManagerParser getLayoutManagerParser(final JComponent component) {
        if (component instanceof JSplitPane) {
            return LAYOUT_MANAGER_PARSER_MAP.get(SplitPaneLayoutManagerParser.LAYOUT_MANAGER_NAME);
        }
        final LayoutManager layoutManager = component.getLayout();
        for (ILayoutManagerParser parser : LAYOUT_MANAGER_PARSER_MAP.values()) {
            if (parser.isInstance(layoutManager)) {
                return parser;
            }
        }
        return NullLayoutManagerParser.INSTANCE;
    }

    private static IComponentParser getComponentParser(final String name) {
        if (StringUtils.isEmpty(name)) {
            return NullComponentParser.INSTANCE;
        }
        final IComponentParser componentParser = COMPONENT_PARSER_MAP.get(name);
        if (componentParser == null) {
            return NullComponentParser.INSTANCE;
        }
        return componentParser;
    }

    private static LayoutManager createLayoutManager(final String layout) {
        return getLayoutManagerParser(layout).newLayoutManager();
    }

    private static JComponent createComponent(final Element element) {
        final String name = element.getName();
        final IComponentParser componentParser = getComponentParser(name);
        return componentParser.parse(element);
    }

    private static void load(final JComponent parent, final List<Element> elementList, final ComponentMap componentMap) {
        if (CollectionUtils.isEmpty(elementList)) {
            return;
        }
        final ILayoutManagerParser constraintParser = getLayoutManagerParser(parent);
        for (Element element : elementList) {
            final JComponent component = createComponent(element);
            if (component == null) {
                return;
            }
            setCommonAttribute(element, component);
            final String id = element.attributeValue("id");
            if (StringUtils.isNotEmpty(id)) {
                final Map<String, String> data = getData(element);
                componentMap.setComponent(id, component, data);
            }
            if (parent instanceof JScrollPane) {
                ((JScrollPane) parent).setViewportView(component);
                continue;
            }
            final Object constraint = constraintParser.parse(element.attributeValue("style"));
            if (constraint == null) {
                parent.add(component);
            } else {
                parent.add(component, constraint);
            }
            load(component, element.elements(), componentMap);
        }
    }

    private static Map<String, String> getData(final Element element) {
        final Map<String, String> data = new TreeMap<>();
        for (Attribute attribute : element.attributes()) {
            final String name = attribute.getName();
            if (name.startsWith("data-")) {
                data.put(name.substring(5), attribute.getValue());
            }
        }
        return data;
    }

    private static void setCommonAttribute(final Element from, final JComponent to) {
        final String preferredSize = from.attributeValue("preferred-size");
        if (StringUtils.isNotEmpty(preferredSize)) {
            to.setPreferredSize(ParserUtils.newDimension(preferredSize));
        }
        final LayoutManager layoutManager = createLayoutManager(from.attributeValue("layout"));
        if (layoutManager != null) {
            to.setLayout(layoutManager);
        }
        final Border border = createBorder(from.attributeValue("border"));
        if (border != null) {
            to.setBorder(border);
        }
    }

    private static Border createBorder(final String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        final AttributeValueMap map = ParserUtils.newAttributeValueMap(value);
        final String type = map.getString("type");
        if ("empty".equals(type)) {
            int top = 0;
            int left = 0;
            int bottom = 0;
            int right = 0;
            if (map.containsKey("top")) {
                top = map.getInteger("top", 0);
                left = top;
                bottom = top;
                right = top;
            }
            if (map.containsKey("left")) {
                left = map.getInteger("left", 0);
                right = left;
            }
            if (map.containsKey("bottom")) {
                bottom = map.getInteger("bottom", 0);
            }
            if (map.containsKey("right")) {
                right = map.getInteger("right", 0);
            }
            return BorderFactory.createEmptyBorder(top, left, bottom, right);
        }
        return null;
    }

    public XMLLoader() {
        super();
    }
}
